#include "scripting.hpp"

#include "res_manager.hpp"
#include "render.hpp"
#include "object_manager.hpp"
#include "logger.hpp"
#include "core/types.hpp"
#include "core/object.hpp"
#include "f_engine.hpp"

extern "C"
{
#include "lua.h"
}

#include <luabind/luabind.hpp>
#include <luabind/function.hpp>
#include <luabind/class.hpp>
#include <luabind/tag_function.hpp>

#include <boost/bind.hpp>
#include <boost/shared_ptr.hpp>

//script resource class implementation
boost::shared_ptr<script_t> script_t::create(boost::shared_ptr<std::stringstream> &stream)
{
    boost::shared_ptr<script_t> ret(new script_t);
    ret->source_ = stream->str();
    return ret;
}

boost::shared_ptr<scripting_instance_t> scripting_instance_t::create(f_engine_t *engine, const std::string &script_name)
{
    boost::shared_ptr<scripting_instance_t> ret(new scripting_instance_t(engine));
    ret->script_ = engine->system<res_manager_t> ().get_res<script_t> (std::string("scripts/") + script_name);
    return ret;
}

//scripting instance class implementation
scripting_instance_t::scripting_instance_t(f_engine_t *engine) :
    engine_(engine)
{
    L_ = lua_open();
    luabind::open(L_);
}

scripting_instance_t::~scripting_instance_t()
{
    lua_close(L_);
}

bool scripting_instance_t::run_script()
{
    try
    {
        logger::log(DEBUG) << "[Scripting] Begin script execution";
        if (luaL_dostring(L_, script_->source_.c_str()))
            throw luabind::error(L_);
        logger::log(DEBUG) << "[Scripting] End script execution";
        return true;
    }
    catch (const luabind::error& e)
    {
        luabind::object error_msg(luabind::from_stack(e.state(), -1));
        logger::log(CRITICAL) << "[Scripting] Lua error: " << error_msg;
    }
    catch (const std::exception& e)
    {
        logger::log(CRITICAL) << "[Scripting] General error: " << e.what();
    }
    catch (...)
    {
        logger::log(CRITICAL) << "[Scripting] Unknown error! ";
    }
    return false;
}

void scripting_instance_t::register_common()
{
    using namespace luabind;

    module(L_)[
               class_<vector3_t> ("vector3_t")
                   .def(constructor<scalar_t, scalar_t, scalar_t> ())
                   .def_readwrite("x", &vector3_t::x)
                   .def_readwrite("y", &vector3_t::y)
                   .def_readwrite("z", &vector3_t::z),

               class_<quaternion_t> ("quaternion_t")
                   .def(constructor<scalar_t, scalar_t, scalar_t, scalar_t> ())
                   .def_readwrite("w", &quaternion_t::w)
                   .def_readwrite("x", &quaternion_t::x)
                   .def_readwrite("y", &quaternion_t::y)
                   .def_readwrite("z", &quaternion_t::z),

               class_<color_t> ("color_t")
                   .def(constructor<scalar_t, scalar_t, scalar_t, scalar_t> ())
                   .def_readwrite("r", &color_t::r)
                   .def_readwrite("g", &color_t::g)
                   .def_readwrite("b", &color_t::b)
                   .def_readwrite("alpha", &color_t::alpha),

               class_<object_t, boost::shared_ptr<object_t> > ("object_t")
                   .def("move_to", &object_t::move_to)
                   .def_readonly("ID", &object_t::ID),

               def("log", tag_function<void(const std::string&)> (boost::bind(&logger::log, DEBUG, _1)))
    ];

}

void scripting_instance_t::register_render_api()
{
    using namespace luabind;

    render_t &render = engine_->system<render_t> ();

    module(L_)[
               def("create_camera", tag_function<void(const std::string&)> (boost::bind(&render_t::create_camera, &render, _1))),

               def("set_back_color", tag_function<void(const color_t&)> (boost::bind(&render_t::set_back_color, &render, _1))),

               def("set_camera", tag_function<void(const vector3_t&)> (boost::bind(&render_t::set_camera, &render, _1))),

               def("camera_look_at", tag_function<void(const vector3_t&)> (boost::bind(&render_t::camera_look_at, &render, _1))),

               def("set_near_clip_dist", tag_function<void(float)> (boost::bind(&render_t::set_near_clip_dist, &render, _1))),

               def("set_far_clip_dist", tag_function<void(float)> (boost::bind(&render_t::set_far_clip_dist, &render, _1))),

               def("set_ambient_light", tag_function<void(const color_t&)> (boost::bind(&render_t::set_ambient_light, &render, _1))),

               def("create_directional_light", tag_function<void(const color_t&, const color_t&, const vector3_t&)> (boost::bind(&render_t::create_directional_light, &render, _1, _2, _3)))
    ];
}

void scripting_instance_t::register_object_api()
{
    using namespace luabind;

    object_manager_t &object_manager = engine_->system<object_manager_t> ();

    module(L_)[
               def("create_object", tag_function<boost::shared_ptr<object_t>(const std::string&, const vector3_t&, const quaternion_t&)> (boost::bind(&object_manager_t::create_object, &object_manager, _1, _2, _3))),
               def("delete_object", tag_function<bool(boost::shared_ptr<object_t>)> (boost::bind(&object_manager_t::delete_object, &object_manager, _1)))
    ];
}

